/******************************************************************************
  Filename:       OSAL_Clock.c
  Revised:        $Date: 2014-06-30 16:38:56 -0700 (Mon, 30 Jun 2014) $
  Revision:       $Revision: 39297 $

  Description:    OSAL Clock definition and manipulation functions.
******************************************************************************/

/*********************************************************************
 * INCLUDES
 */
#include "OSAL.h"

#include "OSAL_Timers.h"
#include "OSAL_Clock.h"
#include "OSAL_Mutex.h"

#include "clk.h"

#include "printf.h"

/*********************************************************************
 * MACROS
 */

/*********************************************************************
 * CONSTANTS
 */

/* Check Below for an explanation */
#define COUNTER_TICK320US 204775UL 

/* converted COUNTER_TICK320US from 320us ticks to ms */
#define COUNTER_ELAPSEDMS 65528UL   

/* 
 * Each tick is 320us so a value greater than 3 implies 
 * that atleast one millisecond has elapsed 320us*4 > 1 ms 
 */
#define TIMER_CLOCK_UPDATE 4 

/*********************************************************************
 * TYPEDEFS
 */

/*********************************************************************
 * GLOBAL VARIABLES
 */

/*********************************************************************
 * EXTERNAL VARIABLES
 */

/*********************************************************************
 * EXTERNAL FUNCTIONS
 */
#ifndef USE_SYSTICK_IRQ
extern uint32 macMcuPrecisionCount(void);
#endif

#if (defined HAL_MCU_CC2430) || (defined HAL_MCU_CC2530) || (defined HAL_MCU_CC2533)

/*  This function is used to divide a 31 bit dividend by a 16 bit
 *  divisor and return a packed 16 bit quotient and 16 bit
 *  remainder.
 *
 *  Note: This routine takes ~25.6us @32MHz. With C overhead, the
 *        time is ~32us.
 *
 *  dividend - 31 bit dividend.
 *  divisor - 16 bit divisor.
 *
 *  return - MSW divisor; LSW quotient
 */
  extern __near_func uint32 osalMcuDivide31By16To16( uint32 dividend, uint16 divisor );

  #define CONVERT_320US_TO_MS_ELAPSED_REMAINDER( x, y, z ) st( \
                                                               \
    /* The 16 bit quotient is in MSW and */                    \
    /* the 16 bit remainder is in LSW. */                      \
    x = osalMcuDivide31By16To16( x, 25 );                      \
                                                               \
    /* Add quotient to y */                                    \
    y += (x >> 16);                                            \
                                                               \
    /* Copy remainder to z */                                  \
    z = (uint16)(x & 0x0FFFF);                                 \
  )
  
  #define CONVERT_MS_TO_S_ELAPSED_REMAINDER( x, y, z ) st(     \
                                                               \
    /* The 16 bit quotient is in MSW and */                    \
    /* the 16 bit remainder is in LSW. */                      \
    x = osalMcuDivide31By16To16( x, 1000 );                    \
                                                               \
    /* Add quotient to y */                                    \
    y += (x >> 16);                                            \
                                                               \
    /* Copy remainder to z */                                  \
    z = (uint16)(x & 0x0FFFF);                                 \
  )

#else /* (defined HAL_MCU_CC2430) || (defined HAL_MCU_CC2530) || (defined HAL_MCU_CC2533) */

#define CONVERT_320US_TO_MS_ELAPSED_REMAINDER( x, y, z ) st( \
  y += x / 25;                                               \
  z = x % 25;                                                \
)
  
#define CONVERT_MS_TO_S_ELAPSED_REMAINDER( x, y, z ) st(     \
  y += x / 1000;                                             \
  z = x % 1000;                                              \
)
#endif /* (defined HAL_MCU_CC2430) || (defined HAL_MCU_CC2530) || (defined HAL_MCU_CC2533) */

/*********************************************************************
 * LOCAL VARIABLES
 */

#ifndef USE_SYSTICK_IRQ
static uint32 previousMacTimerTick = 0;
static uint16 remUsTicks = 0;
#endif

static uint32 timeMSec = 0;

// number of seconds since 0 hrs, 0 minutes, 0 seconds, on the
// 1st of January 2000 UTC
extern CLK_TS_SEC Clk_TS_UTC_sec;

/*********************************************************************
 * LOCAL FUNCTION PROTOTYPES
 */

static void osalClockUpdate( uint32 elapsedMSec );

/*********************************************************************
 * FUNCTIONS
 *********************************************************************/

#ifndef USE_SYSTICK_IRQ
/*********************************************************************
 * @fn      osalTimeUpdate
 *
 * @brief   Uses the free running rollover count of the MAC backoff timer;
 *          this timer runs freely with a constant 320 usec interval.  The
 *          count of 320-usec ticks is converted to msecs and used to update
 *          the OSAL clock and Timers by invoking osalClockUpdate() and
 *          osalTimerUpdate().  This function is intended to be invoked
 *          from the background, not interrupt level.
 *
 * @param   None.
 *
 * @return  None.
 */
void osalTimeUpdate( void )
{
  /* Note that when ICall is in use the OSAL tick is not updated
   * in this fashion but rather through real OS timer tick. */
  halIntState_t intState;
  uint32 tmp;
  uint32 ticks320us;
  uint32 elapsedMSec = 0;

  HAL_ENTER_CRITICAL_SECTION(intState);
  // Get the free-running count of 320us timer ticks
  tmp = macMcuPrecisionCount();
  HAL_EXIT_CRITICAL_SECTION(intState);
  
  if ( tmp != previousMacTimerTick )
  {
    // Calculate the elapsed ticks of the free-running timer.
    ticks320us = (tmp - previousMacTimerTick) & 0xffffffffu;

    if (ticks320us >= TIMER_CLOCK_UPDATE )
    {
      // Store the MAC Timer tick count for the next time through this function.
      previousMacTimerTick = tmp;
      
      /*
       * remUsTicks can have a maximum value of 24 (Since remusTicks got by mod 
       * of 25). The value of COUNTER_TICK320US is a multiple of 25 and the 
       * quotient of  CONVERT_320US_TO_MS_ELAPSED_REMAINDER() does not exceed 
       * 0xFFFF or 16 bit.
       */
      while(ticks320us >= COUNTER_TICK320US)
      {
        ticks320us  -= COUNTER_TICK320US;
        elapsedMSec += COUNTER_ELAPSEDMS;
      }
    
      // update converted number with remaining ticks from loop and the
      // accumulated remainder from loop
      tmp = (ticks320us * 8) + remUsTicks;

      // Convert the 320 us ticks into milliseconds and a remainder
      CONVERT_320US_TO_MS_ELAPSED_REMAINDER( tmp, elapsedMSec, remUsTicks );
      
      // Update OSAL Clock and Timers
      osalClockUpdate( elapsedMSec );
      osalTimerUpdate( elapsedMSec );
      osalMutexUpdate( elapsedMSec );
    }
  }
}
#endif

/*********************************************************************
 * @fn      osalClockUpdate
 *
 * @brief   Updates the OSAL Clock time with elapsed milliseconds.
 *
 * @param   elapsedMSec - elapsed milliseconds
 *
 * @return  none
 */
static void osalClockUpdate( uint32 elapsedMSec )
{
  uint32 tmp;
  halIntState_t intState;
  
  HAL_ENTER_CRITICAL_SECTION(intState);
  // Add elapsed milliseconds to the saved millisecond portion of time
  timeMSec += elapsedMSec;

  // Roll up milliseconds to the number of seconds
  if ( timeMSec >= 1000 )
  {
    tmp = timeMSec;
    CONVERT_MS_TO_S_ELAPSED_REMAINDER(tmp, Clk_TS_UTC_sec, timeMSec);
  }
  HAL_EXIT_CRITICAL_SECTION(intState);
}

/*********************************************************************
 * @fn      osalClockInit
 *
 * @brief   Initialize Clock module.
 *
 * @param   none
 *
 * @return  none
 */
void osalClockInit( void )
{
  CLK_ERR err;

  Clk_Init(&err);
  
  /* initialize Clock module */
  if ( err != CLK_ERR_NONE) {
      printf("Init clock module error!\r\n");
  }
}

/*********************************************************************
 * @fn      osalAdjustTimer
 *
 * @brief   Updates the OSAL Clock and Timer with elapsed milliseconds.
 *
 * @param   MSec - elapsed milliseconds
 *
 * @return  none
 */
void osalAdjustTimer(uint32 Msec )
{
  /* Disable SysTick interrupts */ 
  SysTickIntDisable(); 
  
  osalClockUpdate(Msec);
  osalTimerUpdate(Msec);
  osalMutexUpdate(Msec);
  
  /* Enable SysTick interrupts */ 
  SysTickIntEnable(); 
}
